﻿//=============================================================================
// Библиотека 'WPFExcelReport' предназначена для связи и передачи данных c PLC
//=============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Excel;
using System.Data;
using System.Xml.Linq;
using System.Xml;

namespace WPFExcelReport.CL
{
    /// <summary>
    /// This class is designed to initialize data
    /// </summary>
    internal class Init
    {
        #region Constants

        #endregion

        #region Fields

        #region General structure

        /// <summary>
        /// Deskr-s for reading variables: key - the name of the variable
        /// </summary>
        private Dictionary<string, HandlItemRead> _hItemRead = new Dictionary<string, HandlItemRead>(100);

        /// <summary>
        /// Deskr-s variables for the record: key - the name of the variable
        /// </summary>
        private Dictionary<string, HandlItemWrite> _hItemWrite = new Dictionary<string, HandlItemWrite>(100);

        /// <summary>
        /// The values of variables to read: key - the name of the variable
        /// </summary>
        private Dictionary<string, object> _iValueRead = new Dictionary<string, object>(100);

        /// <summary>
        /// Addresses of variables to read: key - the name of the variable
        /// </summary>
        private Dictionary<string, string> _iAddrRead = new Dictionary<string, string>(100);

        /// <summary>
        /// Addresses of variables for the record: key - the name of the variable
        /// </summary>
        private Dictionary<string, string> _iAddrWrite = new Dictionary<string, string>(100);

        /// <summary>
        /// Server S7
        /// </summary>
        private S7.S7 _server = new S7.S7();

        #endregion
             
        /// <summary>
        /// Num error
        /// </summary>
        private int _errNum = 0;

        /// <summary>
        /// Description of errors
        /// </summary>
        private string _errTxt = "";
                
        #endregion

        #region Properties

        /// <summary>
        /// Num error
        /// </summary>
        public int ErrNum
        {
            get
            {
                return _errNum;
            }
        }

        /// <summary>
        /// Description of error
        /// </summary>
        public string ErrTxt
        {
            get
            {
                return _errTxt;
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor Init
        /// </summary>
        public Init(Dictionary<string, HandlItemRead> hItemRead,
                    Dictionary<string, HandlItemWrite> hItemWrite,
                    Dictionary<string, object> iValueRead,
                    Dictionary<string, string> iAddrRead,
                    Dictionary<string, string> iAddrWrite,
                    S7.S7 server)
        {
            this._hItemRead = hItemRead;
            this._hItemWrite = hItemWrite;
            this._iValueRead = iValueRead;
            this._iAddrRead = iAddrRead;
            this._iAddrWrite = iAddrWrite;
            this._server = server;
        }

        #endregion

        #region Internal methods

        /// <summary>
        /// Parse err
        /// </summary>
        private void ParseErrCode(ErrCode err, string addr = "")
        {
            switch (err)
            {
                case ErrCode.Ok:
                    _errNum = (int)ErrCode.Ok;
                    _errTxt = "ОК";
                    break;

                case ErrCode.InitFileIncorrect:
                    _errNum = (int)ErrCode.InitFileIncorrect;
                    _errTxt = "The initialization file is not valid";
                    break;

                case ErrCode.InitFileNotFound:
                    _errNum = (int)ErrCode.InitFileIncorrect;
                    _errTxt = "The initialization file is not found";
                    break;

                case ErrCode.XmlNoNode:
                    _errNum = (int)ErrCode.XmlNoNode;
                    _errTxt = "The XML file is not valid - the absence subsites Item";
                    break;

                case ErrCode.SameName:
                    _errNum = (int)ErrCode.SameName;
                    _errTxt = "Init-file s is not valid - the variable name " + addr + " repeated";
                    break;

                case ErrCode.SameAddr:
                    _errNum = (int)ErrCode.SameAddr;
                    _errTxt = "Init-file s is not valid - the variable addr " + addr + " repeated";
                    break;

                case ErrCode.ItemReadNoCorrect:
                    _errNum = (int)ErrCode.ItemReadNoCorrect;
                    _errTxt = "Init-file s is not valid - " + addr + " address of a variable to record not valid";
                    break;

                case ErrCode.ItemWriteNoCorrect:
                    _errNum = (int)ErrCode.ItemWriteNoCorrect;
                    _errTxt = "Init-file s is not valid - " + addr + " address of a variable to record not valid";
                    break;

                case ErrCode.VarTypeOther:
                    _errNum = (int)ErrCode.VarTypeOther;
                    _errTxt = "variable type " + addr + " not valid";
                    break;

            }
        }

        /// <summary>
        /// Cleaning collections
        /// </summary>
        private void ClearCollection()
        {
            _hItemRead.Clear();
            _hItemWrite.Clear();

            _iAddrRead.Clear();
            _iValueRead.Clear();
            _iAddrWrite.Clear();
           
            _server.ClearItemsRead();
            _server.ClearItemsWrite();
        }

        /// <summary>
        /// Init from txt file
        /// </summary>
        /// <param name="path"> path to file init</param>
        private ErrCode InitFromTxt(string path)
        {
            ClearCollection();
                       
            List<string> strTxt = new List<string>(100);
            try
            {
                StreamReader sr = File.OpenText(path);

                while (!sr.EndOfStream) strTxt.Add(sr.ReadLine());

                sr.Close();
            }
            catch (FileNotFoundException ex)
            {
                ParseErrCode(ErrCode.InitFileNotFound);
                return ErrCode.InitFileNotFound;
            }
                      
            // *** заполнение колл-ий переменных 
            bool readDataEna = false;
            bool writeDataEna = false;
            List<ItemRead> itemsRead = new List<ItemRead>(100);
            List<ItemWrite> itemsWrite = new List<ItemWrite>(100);
            Dictionary<string, string> iAddrRead = new Dictionary<string, string>(100);
            Dictionary<string, string> iAddrWrite = new Dictionary<string, string>(100);
            ErrCode err;

            foreach (string st in strTxt)
            {
                if (st.Trim().ToLower() == "read_data")
                {
                    readDataEna = true;
                    writeDataEna = false;
                    continue;
                }

                if (st.Trim().ToLower() == "write_data")
                {
                    writeDataEna = true;
                    readDataEna = false;
                    continue;
                }

                if (readDataEna)
                {
                    if ((st.Trim().Length < 5) || (st.Trim().Substring(0, 2) == "//")) continue;

                    string[] words = st.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (words.Count() > 1)
                    {
                        string addr = words[0];
                        string name = words[1];

                        err = CheckIRead(addr, name, itemsRead, iAddrRead);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.read);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }
                }

                if (writeDataEna)
                {
                    if ((st.Trim().Length < 5) || (st.Trim().Substring(0, 2) == "//")) continue;

                    string[] words = st.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (words.Count() > 1)
                    {
                        string addr = words[0];
                        string name = words[1];                       

                        err = CheckIWrite(addr, name, itemsWrite, iAddrWrite);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.write);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }
                }
            }

            // добавляем переменные
            foreach(KeyValuePair<string, string> pair in iAddrRead) _iAddrRead.Add(pair.Key, pair.Value);
            foreach (KeyValuePair<string, string> pair in iAddrWrite) _iAddrWrite.Add(pair.Key, pair.Value);
            foreach (string name in iAddrRead.Keys) _iValueRead.Add(name, new object());

            bool error;
            List<HandlItemRead> hRead = _server.AddItemsRead(itemsRead, out error);
            foreach (HandlItemRead handl in hRead) _hItemRead.Add(handl.Name, handl);

            List<HandlItemWrite> hWrite = _server.AddItemsWrite(itemsWrite, out error);
            foreach (HandlItemWrite handl in hWrite) _hItemWrite.Add(handl.Name, handl);

            return ErrCode.Ok;
        }

        /// <summary>
        /// Init from xlsx file
        /// </summary>
        /// <param name="path"> path to file init</param>
        private ErrCode InitFromXlsx(string path)
        {
            ClearCollection();

            FileStream stream;
            try
            {
                stream = File.Open(path, FileMode.Open, FileAccess.Read);
            }
            catch (FileNotFoundException ex)
            {
                ParseErrCode(ErrCode.InitFileNotFound);
                return ErrCode.InitFileNotFound;
            }

            IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
            excelReader.IsFirstRowAsColumnNames = true;
            DataSet dataSet = excelReader.AsDataSet();
            stream.Close();

            // *** заполнение колл-ий переменных 
            List<ItemRead> itemsRead = new List<ItemRead>(100);
            List<ItemWrite> itemsWrite = new List<ItemWrite>(100);
            Dictionary<string, string> iAddrRead = new Dictionary<string, string>(100);
            Dictionary<string, string> iAddrWrite = new Dictionary<string, string>(100);
            ErrCode err;

            foreach (DataRow dRow in dataSet.Tables[0].Rows)
            {
                string addr = dRow[0].ToString();
                string name = dRow[1].ToString();
                string typeRW = dRow[3].ToString().ToLower();

                if ((addr != "") && (name != ""))
                {
                    if (typeRW == "read")
                    {
                        err = CheckIRead(addr, name, itemsRead, iAddrRead);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.read);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }

                    if (typeRW == "write")
                    {
                        err = CheckIWrite(addr, name, itemsWrite, iAddrWrite);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.write);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }

                    ParseErrCode(ErrCode.VarTypeOther, addr);
                    return ErrCode.VarTypeOther;
                }
            }

            // добавляем переменные
            foreach (KeyValuePair<string, string> pair in iAddrRead) _iAddrRead.Add(pair.Key, pair.Value);
            foreach (KeyValuePair<string, string> pair in iAddrWrite) _iAddrWrite.Add(pair.Key, pair.Value);
            foreach (string name in iAddrRead.Keys) _iValueRead.Add(name, new object());

            bool error;
            List<HandlItemRead> hRead = _server.AddItemsRead(itemsRead, out error);
            foreach (HandlItemRead handl in hRead) _hItemRead.Add(handl.Name, handl);

            List<HandlItemWrite> hWrite = _server.AddItemsWrite(itemsWrite, out error);
            foreach (HandlItemWrite handl in hWrite) _hItemWrite.Add(handl.Name, handl);

            return ErrCode.Ok;
        }

        /// <summary>
        /// Init from xml file
        /// </summary>
        /// <param name="path"> path to file init</param>
        private ErrCode InitFromXml(string path)
        {
            ClearCollection();

            XDocument xDoc = new XDocument();
            try
            {
                xDoc = XDocument.Load(path);
            }
            catch (FileNotFoundException ex)
            {
                ParseErrCode(ErrCode.InitFileNotFound);
                return ErrCode.InitFileNotFound;
            }
            catch (XmlException ex)
            {
                ParseErrCode(ErrCode.InitFileIncorrect);
                return ErrCode.InitFileIncorrect;
            }

            // берем теги 
            IEnumerable<XElement> items = xDoc.Descendants("Item");

            if (items.Count() == 0) return ErrCode.XmlNoNode;

            // *** заполнение колл-ий переменных 
            List<ItemRead> itemsRead = new List<ItemRead>(100);
            List<ItemWrite> itemsWrite = new List<ItemWrite>(100);
            Dictionary<string, string> iAddrRead = new Dictionary<string, string>(100);
            Dictionary<string, string> iAddrWrite = new Dictionary<string, string>(100);
            ErrCode err;

            foreach (XElement item in items)
            {
                string name = item.Element("Name").Value;
                string addr = item.Element("Addr").Value;
                string typeRW = item.Element("TypeRW").Value.ToLower();
              
                if ((addr != "") && (name != ""))
                {
                    if (typeRW == "read")
                    {
                        err = CheckIRead(addr, name, itemsRead, iAddrRead);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.read);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }

                    if (typeRW == "write")
                    {
                        err = CheckIWrite(addr, name, itemsWrite, iAddrWrite);
                        if (err != ErrCode.Ok) return err;

                        err = CheckAddr(addr, TypeRW.write);
                        if (err != ErrCode.Ok) return err;
                        continue;
                    }

                    ParseErrCode(ErrCode.VarTypeOther, addr);
                    return ErrCode.VarTypeOther;
                }
            }

            // добавляем переменные
            foreach (KeyValuePair<string, string> pair in iAddrRead) _iAddrRead.Add(pair.Key, pair.Value);
            foreach (KeyValuePair<string, string> pair in iAddrWrite) _iAddrWrite.Add(pair.Key, pair.Value);
            foreach (string name in iAddrRead.Keys) _iValueRead.Add(name, new object());

            bool error;
            List<HandlItemRead> hRead = _server.AddItemsRead(itemsRead, out error);
            foreach (HandlItemRead handl in hRead) _hItemRead.Add(handl.Name, handl);

            List<HandlItemWrite> hWrite = _server.AddItemsWrite(itemsWrite, out error);
            foreach (HandlItemWrite handl in hWrite) _hItemWrite.Add(handl.Name, handl);

            return ErrCode.Ok;

        }

        /// <summary>
        /// Проверка для добавления переменной для чтения
        /// </summary>
        ///<param name="addr"> адрес переменной</param>
        ///<param name="name"> имя переменной</param>
        ///<param name="itemsRead"> элементы для сервера</param>
        ///<param name="iAddrRead"> адреса переменных </param>
        private ErrCode CheckIRead(string addr, string name, List<ItemRead> itemsRead, Dictionary<string, string> iAddrRead)
        {
            // проверка на совпадение имени
            if (iAddrRead.ContainsKey(name))
            {
                ParseErrCode(ErrCode.SameName, name);
                return ErrCode.SameName;
            }

            // проверка на совпадение адреса
            if (iAddrRead.ContainsValue(addr))
            {
                ParseErrCode(ErrCode.SameAddr, addr);
                return ErrCode.SameAddr;
            }

            // добавляем переменные
            iAddrRead.Add(name, addr);
            itemsRead.Add(new ItemRead(name, addr));

            return ErrCode.Ok;
        }

        /// <summary>
        /// Проверка для добавления переменной для записи
        /// </summary>
        ///<param name="addr"> адрес переменной</param>
        ///<param name="name"> имя переменной</param>
        ///<param name="itemsWrite"> элементы для сервера</param>
        ///<param name="iAddrWrite"> адреса переменных </param>
        private ErrCode CheckIWrite(string addr, string name, List<ItemWrite> itemsWrite, Dictionary<string, string> iAddrWrite)
        {
            // проверка на совпадение имени
            if (iAddrWrite.ContainsKey(name))
            {
                ParseErrCode(ErrCode.SameName, name);
                return ErrCode.SameName;
            }

            // проверка на совпадение адреса
            if (iAddrWrite.ContainsValue(addr))
            {
                ParseErrCode(ErrCode.SameAddr, addr);
                return ErrCode.SameAddr;
            }

            // добавляем переменные
            iAddrWrite.Add(name, addr);
            itemsWrite.Add(new ItemWrite(name, addr));

            return ErrCode.Ok;
        }

        /// <summary>
        /// Проверка адреса переменной
        /// </summary>
        ///<param name="addr"> адрес</param>
        ///<param name="typeRW"> тип переменной</param>
        ///<returns> ошибка</returns>
        private ErrCode CheckAddr(string addr, TypeRW typeRW)
        {
            if (!_server.IsAddrValid(addr))
            {
                if (typeRW == TypeRW.read)
                {
                    ParseErrCode(ErrCode.ItemReadNoCorrect, addr);
                    return ErrCode.ItemReadNoCorrect;
                }
                else
                {
                    ParseErrCode(ErrCode.ItemWriteNoCorrect, addr);
                    return ErrCode.ItemWriteNoCorrect;
                }
            }

            return ErrCode.Ok;

        }
        
        #endregion

        #region External methods

        /// <summary>
        /// Разбор данных для чтения/записи
        /// </summary>
        /// <param name="initFile"> тип файла иниц-ии</param>
        /// <param name="path"> путь к файлу иниц-ии</param>
        ///<returns> ошибка</returns>
        public ErrCode ParseData(string path, TypeInitFile initFile)
        {
            switch (initFile)
            {
                case TypeInitFile.txt:
                    return InitFromTxt(path);
                    
                case TypeInitFile.xlsx:
                    return InitFromXlsx(path);
                    
                case TypeInitFile.xml:
                    return InitFromXml(path);
                    
                default: return ErrCode.InitFileIncorrect;
            }
        }

        #endregion
               
    }

}
